#! /usr/bin/env -S awk -f
# Copyright 2019-2024 Mark Callow
# SPDX-License-Identifier: Apache-2.0

# Usage: mkvkformatfiles [output-dir path/to/vulkan_core]
#
# This is expected to be run in the repo's root directory which is set
# as the working directory by the cmake commands that run this script.
#
# output_dir defaults to the current directory.
# path/to/vulkan_core defaults to ENVIRON["Vulkan_INCLUDE_DIR"]. If not
# set, default is external/dfdutils/vulkan/vulkan_core.h.
#
# When specifying path/to/vulkan_core, output-dir must also be
# specified.

# This script creates 6 files from vulkan/vulkan_core.h:
#
# - lib/src/vkformat_enum.h: the VkFormat enum declaration for those who don't
#   want to include vulkan_core.h. extension token names for
#   extensions that have been moved to core are omitted.
#
# - lib/src/vkformat_str.c: a switch statement for converting VkFormat enums to
#   to strings corresponding to the format names.
# - lib/vkformat_typesize.c, function to return the type-size for each
#   format.
#
# - lib/src/vkformat_check.c: 3 functions: 1 to check if a format is prohibited in
#   KTX2, one to see if a format is a valid Vulkan format and one to see
#   if it is an sRGB format.
#
# - interface/java_binding/src/main/java/org/khronos/ktx/VkFormat.java,
#   VkFormat enumerators for Java.
#
# - interface/js_binding/vk_format.inl,
#   VkFormat enumeration body for ktx_wrapper.cpp.
#
# - interface/python_binding/pyktx/vk_format.py, VkFormat enumerators for
#   Python.
#
# - tests/unittests/vkformat_list.inl, a list of VkFormat enum names for use in
#   initializing tables, etc.
#
# KTX v2 prohibited formats are excluded from the last three files.

BEGIN {
    if (index(tolower(ENVIRON["OS"]), "windows") > 0) {
        ORS = "\r\n";
    }
    # ARGV[0] is "awk".
    if (ARGC > 1) {
      output_dir = ARGV[1] "/";
      delete ARGV[1];
    } else {
      output_dir = "./";
    }
    # awk reads files whose names are found in the positional parameters,
    # hence setting ARGV[1] here and deleting a possible ARGV[1] above.
    if (ARGC == 1) {
      if (ENVIRON["Vulkan_INCLUDE_DIR"]) {
          ARGV[1] = ENVIRON["Vulkan_INCLUDE_DIR"] "/vulkan/vulkan_core.h"; ARGC = 2
      } else {
  # Use local vulkan_core.h until ASTC 3D texture extension is released.
  #       ARGV[1] = "/usr/include";
          ARGV[1] = "external/dfdutils/vulkan/vulkan_core.h"; ARGC = 2
      }
    }

    # Hard-coded paths are somewhat unfortunate but major changes in
    # the source code organization are unlikely.
    format_hdr = output_dir "lib/src/vkformat_enum.h"
    format_typesize = output_dir "lib/src/vkformat_typesize.c"
    format_inl = output_dir "tests/unittests/vkformat_list.inl"
    format_strings = output_dir "lib/src/vkformat_str.c"
    format_check = output_dir "lib/src/vkformat_check.c"
    format_java = output_dir "interface/java_binding/src/main/java/org/khronos/ktx/VkFormat.java"
    format_js = output_dir "interface/js_binding/vk_format.inl"
    format_python = output_dir "interface/python_binding/pyktx/vk_format.py"

    vulkan_core = "vulkan_core.h"
    processing_core_formats = 0
    processing_extension_formats = 0
    # Range pattern matches 2nd line to avoid other comments so need
    # comment opening.
    copyright = "/*" ORS
    banner = ""
    format_decl = ""
    format_name_list = ""
    core_formats = ""
    extension_formats = ""
    end_range = ""
    max_std_format_enum = 0
    java = "package org.khronos.ktx;" ORS "public class VkFormat {" ORS
    prohibited = "#include <stdint.h>" ORS "#include <stdbool.h>" ORS ORS
    prohibited = prohibited "#include \"vkformat_enum.h\"" ORS ORS
    prohibited = prohibited "bool" ORS "isProhibitedFormat(VkFormat format)" ORS "{" ORS
    prohibited = prohibited "    switch (format) {" ORS;
    python = "from enum import IntEnum" ORS ORS "class VkFormat(IntEnum):" ORS
    python = python "    \"\"\"Vulkan texture format constants.\"\"\"" ORS ORS
    srgb = "bool" ORS "isSrgbFormat(VkFormat format)" ORS "{" ORS
    srgb = srgb "   switch(format) {" ORS
    valid = "bool" ORS "isValidFormat(VkFormat format)" ORS "{" ORS
    valid = valid "    // On MSVC VkFormat can be a signed integer" ORS
    valid = valid "    if ((uint32_t) format <= VK_FORMAT_MAX_STANDARD_ENUM)" ORS
    valid = valid "        return true;" ORS "    else switch(format) {" ORS
}

# A range pattern to extract the copyright message.
/\*\* Copyright*/,/\*\// { copyright = copyright $0 ORS; }

$2 == "VK_HEADER_VERSION" {
  banner = ORS
  banner = banner "/***************************** Do not edit.  *****************************" ORS
  banner = banner " Automatically generated from " vulkan_core " version " $3 " by mkvkformatfiles." ORS
  banner = banner " *************************************************************************/" ORS
}

# Extract VkFlags definition.
/typedef .* VkFlags/ {
  format_decl = format_decl "#if defined(_MSC_VER) && _MSC_VER < 1900 // Older than VS 2015." ORS
  format_decl = format_decl "typedef unsigned __int32 VkFlags;" ORS "#else" ORS
  format_decl = format_decl "#include <stdint.h>" ORS
  format_decl = format_decl $0 ORS "#endif" ORS ORS
}

# A range pattern to extract the VkFormat declaration.
/^typedef enum VkFormat {/,/^} VkFormat;/ {
  if ($3 !~ /VK_FORMAT_.*/) { # Avoid values defined as existing values.
    format_decl = format_decl $0 ORS
    if ($1 ~ /VK_FORMAT/ && $1 !~ /.*MAX_ENUM/ && $3 !~ /1000....../) {
      # I don't understand why but if I apply the sub to $3 here, it
      # breaks extraction of VK_FORMAT token names below. It is like
      # this $3 becomes the $3 seen down there.
      enum_val = $3;
      sub(/,$/, "", enum_val);
      if (enum_val+0 > max_std_format_enum) {
        max_std_format_enum = enum_val+0;
      }
      if ($1 !~ /UNDEFINED/) {
        format_name_list = format_name_list $1 "," ORS
      }
      if ($1 !~ /SCALED/ && $1) {
        java = java "    public static final int " $1 " = " enum_val ";" ORS
        js = js "    .value(\"" removePrefix($1, "VK_FORMAT_") "\", " $1 ")" ORS
        python = python "    " $1 " = " enum_val ORS
      }
    }
  }
  if ($1 ~ /}/) {
    end_range = "#define VK_FORMAT_MAX_STANDARD_ENUM " max_std_format_enum ORS;
  }
}

/.*SCALED/  { prohibited = prohibited "      case " $1 ":" ORS; }
#/A8B8G8R8_.*_PACK32/  { prohibited = prohibited "      case " $1 ":" ORS; }
# Multiplane formats.
/VK_FORMAT_[^F]/ && (/PLANE/ || /420/) {
  # Avoid values defined as existing values and avoid the MAX_ENUM value.
  if ($3 !~ /VK_FORMAT_.*/ && $1 !~ /.*MAX_ENUM/) {
    prohibited = prohibited "      case " $1 ":" ORS;
  }
}

# Extract srgb formats
/VK_FORMAT_.*SRGB/ && !/PLANE/ && !/420/ {
  srgb = srgb "      case " $1 ":" ORS;
}

# Extract valid formats with values > VK_FORMAT_END_RANGE.
/VK_FORMAT_[^F].* = 1000/ && !/PLANE/ && !/420/ {
  valid = valid "        case " $1 ":" ORS;
  format_name_list = format_name_list $1 "," ORS
  enum_val = $3;
  sub(/,$/, "", enum_val);
  java = java "    public static final int " $1 " = " enum_val ";" ORS
  js = js "    .value(\"" removePrefix($1, "VK_FORMAT_") "\", " $1 ")" ORS
  python = python "    " $1 " = " enum_val ORS
}

function removePrefix(string, prefix) {
  sub("^" prefix, "", string)
  return string
}

function genTypeSize(format) {
  size = format
  if (format ~ /^VK_FORMAT_UNDEFINED$/) {
      size = 1
  } else if (format ~ /.*PACK[0-9]+($|_)/) {
      sub("^.*PACK", "", size)
      sub("_.*$", "", size)
      size = size / 8
  } else if (format ~ /.*BLOCK/) {
      size = 1
  } else if (format ~ /D16_UNORM_S8_UINT/) {
      size = 2
  } else if (format ~ /D24_UNORM_S8_UINT/) {
      size = 4
  } else {
      sub("^[^0-9]+", "", size)
      sub("[^0-9].*$", "", size)
      size = size / 8
  }
  return "      case " format ":" ORS "        return " size ";" ORS
}

# Extract VK_FORMAT token names. [^F] avoids the VK_FORMAT_FEATURE* tokens.
/    VK_FORMAT_[^F]/ {
  switch_value = ""
  if ($1 !~ /.*MAX_ENUM/) { # Avoid the MAX_ENUM value.
    if ($3 !~ /VK_FORMAT_.*/) { # Avoid values defined as existing values.
      switch_body_vk2str = switch_body_vk2str "      case " $1 ":" ORS "        return \"" $1 "\";" ORS
      switch_value = $1 # Use symbolic not numeric value.
      switch_body_vktypesize = switch_body_vktypesize genTypeSize($1)
    } else {
      switch_value = $3 # Use symbol for existing value.
      sub(/,.*$/, "", switch_value)
    }
    switch_body_str2vk = switch_body_str2vk "    if (ktx_strcasecmp(str, \"" removePrefix($1, "VK_FORMAT_") "\") == 0)" ORS "        return " switch_value ";" ORS
  }
}

function write_header_file(guard1, guard2, body, filename) {
    print "// clang-format off: CI is complicated if formatting checks on generated files are enforced." > filename
    if (guard2) {
        print "#if !defined("guard1") && !defined("guard2")" > filename
    } else {
        print "#ifndef "guard1 > filename
    }
    print "#define "guard1 > filename
    print banner > filename
    print copyright > filename
    print body > filename
    print "#endif /* "guard1" */" > filename
    print "// clang-format on" > filename
}

function write_source_file(body, filename) {
    print banner > filename
    print copyright > filename
    # write_source_file used for both java and c sources
    if (filename ~ /\.(h|c)$/) {
      print "// clang-format off: CI is complicated if formatting checks on generated files are enforced." > filename
    }
    print body > filename
    if (filename ~ /\.(h|c)$/) {
      print "// clang-format on" > filename
    }
}

function write_python_source_file(body, filename) {
    pybanner = banner;
    sub(/\/\*/, "#***", pybanner);
    sub(/\*\//, "****", pybanner);
    sub(/ Auto/, "# Auto", pybanner);
    regexp = ORS " \\*";
    sub(regexp, ORS "#", pybanner);

    pycopyright = copyright;
    regexp = "/\\*" ORS;
    sub(regexp, "", pycopyright);
    gsub(/\*\*/, "#", pycopyright);
    regexp = "\\*/" ORS;
    sub(regexp, "", pycopyright);

    print pybanner > filename
    print pycopyright > filename
    print body > filename
}

END {
    # vkformat_enum.h
    write_header_file("_VKFORMAT_ENUM_H_", "VULKAN_CORE_H_", format_decl ORS end_range, format_hdr);

    # vkformat_list.inl
    write_source_file(format_name_list ORS, format_inl)

    # vkformat_typesize.c
    begin_vktypesize = ORS
    begin_vktypesize = begin_vktypesize "#include <stdint.h>" ORS;
    begin_vktypesize = begin_vktypesize ORS;
    begin_vktypesize = begin_vktypesize "#include \"vkformat_enum.h\"" ORS;
    begin_vktypesize = begin_vktypesize ORS;
    begin_vktypesize = begin_vktypesize "uint32_t" ORS "vkFormatTypeSize(VkFormat format)" ORS "{" ORS;
    begin_vktypesize = begin_vktypesize "    switch (format) {" ORS;
    end_vktypesize = "      default:" ORS
    end_vktypesize = end_vktypesize "        return 0;" ORS;
    end_vktypesize = end_vktypesize "    }" ORS
    end_vktypesize = end_vktypesize "}"
    write_source_file(begin_vktypesize switch_body_vktypesize end_vktypesize, format_typesize);

    # vkformat_check.c
    prohibited = prohibited "        return true;" ORS
    prohibited = prohibited "      default:" ORS "        return false;" ORS "    }" ORS "}" ORS;
    srgb = srgb "        return true;" ORS
    srgb = srgb "      default:" ORS "        return false;" ORS "    }" ORS "}" ORS;
    valid = valid "        return true;" ORS
    valid = valid "      default:" ORS "        return false;" ORS "    }" ORS "}" ORS;
    write_source_file(prohibited ORS srgb ORS valid, format_check)

    # vkformat_str.c
    prelude = ORS;
    prelude = prelude "#include <stdint.h>" ORS;
    prelude = prelude "#include <ctype.h>" ORS;
    prelude = prelude ORS;
    prelude = prelude "#include \"vkformat_enum.h\"" ORS;
    prelude = prelude ORS;
    prelude = prelude "const char*" ORS "vkFormatString(VkFormat format)" ORS "{" ORS;
    prelude = prelude "    switch (format) {" ORS;
    postscript = "      default:" ORS "        return \"VK_UNKNOWN_FORMAT\";" ORS;
    postscript = postscript "    }" ORS;
    postscript = postscript "}" ORS;
    begin_str2vk = ORS
    begin_str2vk = begin_str2vk "static int ktx_strcasecmp(const char* s1, const char* s2) {" ORS
    begin_str2vk = begin_str2vk "    const unsigned char* us1 = (const unsigned char*) s1;" ORS
    begin_str2vk = begin_str2vk "    const unsigned char* us2 = (const unsigned char*) s2;" ORS
    begin_str2vk = begin_str2vk ORS
    begin_str2vk = begin_str2vk "    while (tolower(*us1) == tolower(*us2)) {" ORS
    begin_str2vk = begin_str2vk "        if (*us1 == '\\0')" ORS
    begin_str2vk = begin_str2vk "            return 0;" ORS
    begin_str2vk = begin_str2vk "        ++us1;" ORS
    begin_str2vk = begin_str2vk "        ++us2;" ORS
    begin_str2vk = begin_str2vk "    }" ORS
    begin_str2vk = begin_str2vk "    return tolower(*us1) - tolower(*us2);" ORS
    begin_str2vk = begin_str2vk "}" ORS
    begin_str2vk = begin_str2vk ORS
    begin_str2vk = begin_str2vk "static int ktx_strncasecmp(const char* s1, const char* s2, int length) {" ORS
    begin_str2vk = begin_str2vk "    const unsigned char* us1 = (const unsigned char*) s1;" ORS
    begin_str2vk = begin_str2vk "    const unsigned char* us2 = (const unsigned char*) s2;" ORS
    begin_str2vk = begin_str2vk ORS
    begin_str2vk = begin_str2vk "    while (length > 0 && tolower(*us1) == tolower(*us2)) {" ORS
    begin_str2vk = begin_str2vk "        if (*us1 == '\\0')" ORS
    begin_str2vk = begin_str2vk "            return 0;" ORS
    begin_str2vk = begin_str2vk "        ++us1;" ORS
    begin_str2vk = begin_str2vk "        ++us2;" ORS
    begin_str2vk = begin_str2vk "        --length;" ORS
    begin_str2vk = begin_str2vk "    }" ORS
    begin_str2vk = begin_str2vk "    if (length == 0)" ORS
    begin_str2vk = begin_str2vk "        return 0;" ORS
    begin_str2vk = begin_str2vk "    return tolower(*us1) - tolower(*us2);" ORS
    begin_str2vk = begin_str2vk "}" ORS
    begin_str2vk = begin_str2vk ORS
    begin_str2vk = begin_str2vk "/// Parses a VkFormat. VK_FORMAT_ prefix is optional. Case insensitive." ORS
    begin_str2vk = begin_str2vk "VkFormat" ORS
    begin_str2vk = begin_str2vk "stringToVkFormat(const char* str)" ORS
    begin_str2vk = begin_str2vk "{" ORS
    begin_str2vk = begin_str2vk "    if (ktx_strncasecmp(str, \"VK_FORMAT_\", sizeof(\"VK_FORMAT_\") - 1) == 0)" ORS
    begin_str2vk = begin_str2vk "        str += sizeof(\"VK_FORMAT_\") - 1;" ORS
    begin_str2vk = begin_str2vk ORS
    end_str2vk = "    return VK_FORMAT_UNDEFINED;" ORS;
    end_str2vk = end_str2vk "}"
    write_source_file(prelude switch_body_vk2str postscript begin_str2vk switch_body_str2vk end_str2vk, format_strings);

    # VkFormat.java
    end_java = "}" ORS
    write_source_file(java end_java, format_java);

    # vk_format.inl (js)
    write_source_file(js, format_js);

    # vk_format.py
    write_python_source_file(python, format_python);
}

# vim:ai:ts=4:sts=4:sw=2:expandtab:textwidth=70

